package com.study.crypto.signer.utils;

import com.study.crypto.asn1.oid.CMSObjectIdentifiers;
import com.study.crypto.utils.ASN1Utils;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.CMSAttributes;
import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
import org.bouncycastle.asn1.cms.Time;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.crypto.CryptoException;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.cert.CRL;
import java.security.cert.CRLException;
import java.security.cert.X509CRL;
import java.util.Date;

/**
 * CMS 加密签名消息工具类
 * @author Songjin
 * @since 2022-04-25 11:26
 */
public final class CMSUtils {
    
    private CMSUtils() {
    }
    
    /**
     * 组装sm2加密签名消息-签名数据结构。不带属性
     * @param certificate 证书
     * @param signValue 签名值
     * @param contentData 内容
     * @param crls 证书吊销列表
     * @return cms签名值结构
     * @throws CRLException
     * @throws IOException
     */
    public static byte[] compositeSignedData(Certificate certificate, byte[] signValue, byte[] contentData, CRL[] crls) throws CRLException, IOException {
        int version = 1;
        int signerversion = 1;
        ASN1EncodableVector signerInfoVector = new ASN1EncodableVector();
        signerInfoVector.add(new ASN1Integer(signerversion));
        signerInfoVector.add(new IssuerAndSerialNumber(certificate.getIssuer(), certificate.getSerialNumber().getPositiveValue()));
        signerInfoVector.add(new AlgorithmIdentifier(GMObjectIdentifiers.sm3, DERNull.INSTANCE));
        signerInfoVector.add(new AlgorithmIdentifier(GMObjectIdentifiers.sm2sign, DERNull.INSTANCE));
        signerInfoVector.add(new DEROctetString(signValue));
        DERSequence signerInfo = new DERSequence(signerInfoVector);
    
        AlgorithmIdentifier digAlgorithm = new AlgorithmIdentifier(GMObjectIdentifiers.hmac_sm3, DERNull.INSTANCE);
        ASN1EncodableVector innerContentInfoVector = new ASN1EncodableVector();
        innerContentInfoVector.add(CMSObjectIdentifiers.data);
        if (contentData != null) {
            DERTaggedObject content = new DERTaggedObject(0, new DEROctetString(contentData));
            innerContentInfoVector.add(content);
        }
        DERSequence innerContentInfo = new DERSequence(innerContentInfoVector);
    
        ASN1ObjectIdentifier contentTypeSignDataOid = CMSObjectIdentifiers.signedData;
        ASN1EncodableVector signedDataVector = new ASN1EncodableVector();
        signedDataVector.add(new ASN1Integer(version));
        signedDataVector.add(ASN1Utils.createDerSetFromList(digAlgorithm));
        signedDataVector.add(innerContentInfo);
        signedDataVector.add(new DERTaggedObject(false, 0, ASN1Utils.createDerSetFromList(certificate)));
        if (crls != null) {
            ASN1EncodableVector crlVector = new ASN1EncodableVector();
            for (CRL crl : crls) {
                byte[] encoded = ((X509CRL) crl).getEncoded();
                ASN1InputStream inputStream = new ASN1InputStream(new ByteArrayInputStream(encoded));
                crlVector.add(inputStream.readObject());
                inputStream.close();
            }
            DERSequence crlSeq = new DERSequence(crlVector);
            DERSet crlSet = new DERSet(crlSeq);
            signedDataVector.add(new DERTaggedObject(false, 1, crlSet));
        }
        signedDataVector.add(new DERSet(signerInfo));
        DERSequence signedData = new DERSequence(signedDataVector);
    
        ASN1EncodableVector contentInfoVector = new ASN1EncodableVector();
        contentInfoVector.add(contentTypeSignDataOid);
        contentInfoVector.add(new DERTaggedObject(0, signedData));
        return new DERSequence(contentInfoVector).getEncoded();
    }
    
    /**
     * 组装sm2加密签名消息-签名数据结构。带属性
     * @param certificate 证书
     * @param signValue 签名值
     * @param crls 证书吊销列表
     * @param attrVector 属性
     * @return cms签名值结构
     * @throws CRLException
     * @throws IOException
     */
    public static byte[] getAttrSignedData(Certificate certificate, byte[] signValue, CRL[] crls, ASN1EncodableVector attrVector) throws IOException, GeneralSecurityException, CryptoException {
        int version = 1;
        int signerversion = 1;
        ASN1EncodableVector signerInfoVector = new ASN1EncodableVector();
        signerInfoVector.add(new ASN1Integer(signerversion));
        signerInfoVector.add(new IssuerAndSerialNumber(certificate.getIssuer(), certificate.getSerialNumber().getPositiveValue()));
        signerInfoVector.add(new AlgorithmIdentifier(GMObjectIdentifiers.sm3, DERNull.INSTANCE));
        if (attrVector != null) {
            DERSet attributeSequence = new DERSet(attrVector);
            DERTaggedObject derTaggedObject = new DERTaggedObject(false, 0, attributeSequence);
            signerInfoVector.add(derTaggedObject);
        }
        signerInfoVector.add(new AlgorithmIdentifier(GMObjectIdentifiers.sm2sign, DERNull.INSTANCE));
        signerInfoVector.add(new DEROctetString(signValue));
        DERSequence signerInfo = new DERSequence(signerInfoVector);
    
        AlgorithmIdentifier digAlgorithm = new AlgorithmIdentifier(GMObjectIdentifiers.hmac_sm3, DERNull.INSTANCE);
        ASN1EncodableVector innerContentInfoVector = new ASN1EncodableVector();
        innerContentInfoVector.add(CMSObjectIdentifiers.data);
        DERSequence innerContentInfo = new DERSequence(innerContentInfoVector);
    
        ASN1ObjectIdentifier contentTypeSignDataOid = CMSObjectIdentifiers.signedData;
        ASN1EncodableVector signedDataVector = new ASN1EncodableVector();
        signedDataVector.add(new ASN1Integer(version));
        signedDataVector.add(ASN1Utils.createDerSetFromList(digAlgorithm));
        signedDataVector.add(innerContentInfo);
        signedDataVector.add(new DERTaggedObject(false, 0, ASN1Utils.createDerSetFromList(certificate)));
        if (crls != null) {
            ASN1EncodableVector crlVector = new ASN1EncodableVector();
            for (CRL crl : crls) {
                byte[] encoded = ((X509CRL) crl).getEncoded();
                ASN1InputStream inputStream = new ASN1InputStream(new ByteArrayInputStream(encoded));
                crlVector.add(inputStream.readObject());
                inputStream.close();
            }
            DERSequence crlSeq = new DERSequence(crlVector);
            DERSet crlSet = new DERSet(crlSeq);
            signedDataVector.add(new DERTaggedObject(false, 1, crlSet));
        }
        signedDataVector.add(new DERSet(signerInfo));
        DERSequence signedData = new DERSequence(signedDataVector);
    
        ASN1EncodableVector contentInfoVector = new ASN1EncodableVector();
        contentInfoVector.add(contentTypeSignDataOid);
        contentInfoVector.add(new DERTaggedObject(0, signedData));
        return new DERSequence(contentInfoVector).getEncoded();
    }
    
    public static ASN1EncodableVector getAttrVector(ASN1ObjectIdentifier dataOid, byte[] hashedData) {
        Attribute[] attributes = new Attribute[]{
                // new Attribute(CMSAttributes.contentType, new DERSet(dataOid)),
                new Attribute(CMSAttributes.signingTime, new DERSet(new Time(new Date()))),
                new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(hashedData))),
        };
        return ASN1Utils.createAsn1EncodableVector(attributes);
    }
}
